package com.spring.aop;


import com.spring.annotation.*;
import com.spring.aop.interceptor.*;
import com.spring.bean.BeanDefinition;
import com.spring.factory.BeanFactory;
import lombok.Data;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * 保存 切面类对象 ， 4个切面方法 ,  以及代理的目标类方法信息
 */
@Data
public class Advisor {
	private static final String PATTERN = "*";
	
	/**
	 * 切面对象， 用于反射调用时传递参数,需要时创建
	 *
	 * 构建切面通知的时候， aspect 是null  ，因为此时 切面类可能还没有实例化
	 */
	protected Object aspect = null;
	
	/**
	 * aspectName 切面对象bean 名字
	 */
	protected String aspectBeanName;
	
	/**
	 * 4个切面方法保存一下引用
	 */
	protected Method methodBefore;
	protected Method methodAfter;
	protected Method methodAfterReturning;
	protected Method methodAfterThrowing;
	
	
	/**
	 * 代理的目标类，  需要在 beanPostProcess 里面检查每个bean 的 类名是否和它一致
	 */
	protected String targetClassName;
	
	/**
	 * 代理的目标方法，  需要在 beanPostProcess 里面检查每个bean 的 方法是否和它一致
	 */
	protected String targetMethodName;
	
	
	/**
	 * 保存容器，为了后续运行时拿到切面对象实例
	 */
	protected BeanFactory factory;
	
	
	/**
	 * 提供一个方法检查是否和目标 bean 匹配
	 */
	public boolean isMatchBean(Class<?> beanClass) {
		String name = beanClass.getName();
		return targetClassName.equals(name);
	}
	
	/**
	 * 是否和目标方法匹配
	 */
	public boolean isMatchMethod(Method method) {
		// Object 类方法不关心
		if (method.getDeclaringClass() == Object.class) {
			return false;
		}
		
		// 如果代理目标方法直接就是一个 * , 则总是匹配这个方法
		if (PATTERN.equals(targetMethodName)) {
			return true;
		}
		
		//  set*    匹配   setHello   setWorld
		// 用正则匹配下，通配符
		String methodName = method.getName();
		if (targetMethodName.contains(PATTERN)) {
			String reg = targetMethodName.replaceAll("\\*", ".*?");
			return methodName.matches(reg);
		}
		// 其他情况，就直接检查目标方法是否相等了
		return targetMethodName.equals(methodName);
	}
	
	
	// 返回自身构建的 拦截器链
	// 必须严格按照顺序  before  after   after return  after throwing
	public List<Interceptor> getInterceptors() {
		ArrayList<Interceptor> interceptors = new ArrayList<>(6);
		if (methodBefore != null) {
			interceptors.add(new BeforeInterceptor(this));
		}
		if (methodAfter != null) {
			interceptors.add(new AfterInterceptor(this));
		}
		if (methodAfterReturning != null) {
			interceptors.add(new AfterReturningInterceptor(this));
		}
		if (methodAfterThrowing != null) {
			interceptors.add(new AfterThrowingInterceptor(this));
		}
		return interceptors;
	}
	
	/**
	 * 获取切面对象
	 */
	public Object getAspectObj() {
		if (this.aspect == null) {
			this.aspect = factory.getBean(this.aspectBeanName);
		}
		return this.aspect;
	}
	
	
	public static Advisor buildAdvisorIfNecessary(BeanDefinition bd, BeanFactory beanFactory) {
		Class<?> beanClass = bd.getBeanClass();
		Aspect aspect = beanClass.getAnnotation(Aspect.class);
		if (aspect == null) {
			return null;
		}
		
		// 取出所有public方法，只有public方法才会代理， protected private 无法从外部调用
		Method[] methods = beanClass.getMethods();
		Method beforeMethod = Arrays.stream(methods).filter(f -> f.getParameterCount() == 0 && f.isAnnotationPresent(Before.class)).findFirst().orElse(null);
		Method afterMethod = Arrays.stream(methods).filter(f -> f.getParameterCount() == 0 && f.isAnnotationPresent(After.class)).findFirst().orElse(null);
		Method afterReturningMethod = Arrays.stream(methods).filter(f -> f.getParameterCount() == 0 && f.isAnnotationPresent(AfterReturning.class)).findFirst().orElse(null);
		Method afterThrowingMethod = Arrays.stream(methods).filter(f -> f.getParameterCount() == 0 && f.isAnnotationPresent(AfterThrowing.class)).findFirst().orElse(null);
		
		if (beforeMethod == null && afterMethod == null && afterReturningMethod == null && afterThrowingMethod == null) {
			// 一个代理方法都没有，直接返回
			return null;
		}
		
		Advisor advisor = new Advisor();
		// aspectObj 默认为null，应该对象还没创建
		advisor.setAspectBeanName(bd.getBeanName());
		advisor.setMethodBefore(beforeMethod);
		advisor.setMethodAfter(afterMethod);
		advisor.setMethodAfterReturning(afterReturningMethod);
		advisor.setMethodAfterThrowing(afterThrowingMethod);
		advisor.setTargetMethodName(aspect.methodName());
		advisor.setTargetClassName(aspect.className());
		advisor.setFactory(beanFactory);
		
		return advisor;
	}
	
	
	public void before() throws Throwable {
		methodBefore.invoke(getAspectObj());
	}
	
	public void after() throws Throwable {
		methodAfter.invoke(getAspectObj());
	}
	
	public void afterReturning() throws Throwable {
		methodAfterReturning.invoke(getAspectObj());
	}
	
	public void afterThrowing() throws Throwable {
		methodAfterThrowing.invoke(getAspectObj());
	}
}
